package com.xiaoyu.awakening.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xiaoyu.awakening.common.core.dto.Result;
import com.xiaoyu.awakening.gateway.config.GatewaySecurityConfig;
import com.xiaoyu.awakening.gateway.enums.ITokenValidate;
import com.xiaoyu.awakening.gateway.enums.TokenLoginValidateStatus;
import com.xiaoyu.awakening.gateway.enums.TokenValidateStatus;
import com.xiaoyu.awakening.gateway.handler.AbstractTokenVerifyHandler;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;


/**
 * IcourtGatewaySecurityFilter
 *
 * @author wangxiaoyu
 * @since v1.0
 */
@Slf4j
@Component
public class AwkGatewaySecurityFilter implements GlobalFilter, Ordered {
    @Resource
    private GatewaySecurityConfig gatewaySecurityConfig;
    private final PathMatcher pathMatcher = new AntPathMatcher();
    private final static String PROXY_PREFIX = "/proxy";
    private final static String BLANK_STR = " ";


    private final List<AbstractTokenVerifyHandler> tokenVerifyHandlers;

    /**
     * 401跳转到登录页标志
     */
    public static final String REDIRECT_LOGIN_FLAG = "redirectToLogin";

    @Autowired
    public AwkGatewaySecurityFilter(List<AbstractTokenVerifyHandler> tokenVerifyHandlers) {
        this.tokenVerifyHandlers = tokenVerifyHandlers.stream()
                .sorted(Comparator.comparing(AbstractTokenVerifyHandler::order).reversed()).collect(Collectors.toList());
    }


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpRequest request = exchange.getRequest();
            //代理服务直接放行
            if (request.getURI().getPath().startsWith(PROXY_PREFIX)) {
                return chain.filter(exchange);
            }

            //不需要token验证处理直接放行
            boolean notNeedAuthUrl = isUnAuthUrl(request);
            if (notNeedAuthUrl) {
                return successResult(exchange, chain);
            }

            //token解析，非法性校验
            String token = resolveToken(request);
            TokenValidateStatus tokenValidateStatus = validateTokenReturnInfo(gatewaySecurityConfig.getSecret(), token);

            //token转化为TokenUser,上下文存储
            //todo
            if (TokenValidateStatus.TOKEN_PASS.equals(tokenValidateStatus)) {
            }

            //需要token验证处理
            switch (tokenValidateStatus) {
                case TOKEN_EMPTY:
                case TOKEN_PARSE_ERROR:
                case TOKEN_VALIDATE_INVALID:
                case TOKEN_TRANS_TOKENUSER_ERROR:
                    return errorResult(exchange, tokenValidateStatus, "token校验失败,远程IP已经被记录！！");
                case TOKEN_TRANS_TOKENUSER_OK:
                    for (AbstractTokenVerifyHandler tokenVerifyHandler : tokenVerifyHandlers) {
                        if (tokenVerifyHandler.needVerify()) {
                            return tokenVerifyHandler.verify(exchange, chain).flatMap(tokenLoginValidateDto -> {
                                if (Objects.nonNull(tokenLoginValidateDto)) {
                                    if (Objects.nonNull(tokenLoginValidateDto.getUserDefinedDeal())) {
                                        return tokenLoginValidateDto.getUserDefinedDeal();
                                    }
                                    if (TokenLoginValidateStatus.TOKEN_LOGIN_VERIFY_OK.equals(tokenLoginValidateDto.getTokenLoginValidateStatus())) {
                                        return successResult(exchange, chain);
                                    }
                                    return errorResult(exchange, tokenLoginValidateDto.getTokenLoginValidateStatus(), tokenLoginValidateDto.getUserDefinedReturnMag());
                                }
                                return errorResult(exchange, TokenLoginValidateStatus.TOKEN_LOGIN_VERIFY_DEFAULT_ERROR, "");
                            });
                        }
                    }
                default:
                    throw new RuntimeException("oa-gateway error!!!");
            }
        } catch (Exception e) {
            return errorResult(exchange, TokenLoginValidateStatus.TOKEN_LOGIN_VERIFY_DEFAULT_ERROR, "");
        } finally {
        }
    }

    /**
     * 获取token
     * 1、优先：从header中获取
     * 2、其次：从URL获取
     * 3、最后：从cookie获取
     *
     * @param request http request
     * @author wangxiaoyu
     * @since v1.0
     */
    private String resolveToken(ServerHttpRequest request) {
        try {
            String headKey = gatewaySecurityConfig.getAuthorizationHeader();
            //先从head取token
            HttpHeaders headers = request.getHeaders();
            String token = headers.getFirst(headKey);
            HttpMethod method = request.getMethod();
            //如果token为null，并且是get请求，则从参数再获取一次
            if (StrUtil.isBlank(token) && HttpMethod.GET.matches(method.name())) {
                MultiValueMap<String, String> queryParams = request.getQueryParams();
                token = queryParams.getFirst(headKey);
            }
            //如果token为null，则从cookie里再获取一次
            if (StrUtil.isBlank(token)) {
                HttpCookie tokenCookie = request.getCookies().getFirst(headKey);
                token = Objects.nonNull(tokenCookie) ? tokenCookie.getValue() : token;
            }
            return token;
        } catch (Exception e) {
            log.error("resolveToken error:", e);
        }
        return null;
    }

    /**
     * 用以前的算法校验token
     *
     * @param secret secret
     * @param token  token
     * @return TokenValidateStatus
     */
    private TokenValidateStatus validateTokenReturnInfo(String secret, String token) {
        return TokenValidateStatus.TOKEN_PASS;
    }

    /**
     * UnAuthUrl 判断
     *
     * @param request request
     * @author wangxiaoyu
     * @since v1.0
     */
    protected boolean isUnAuthUrl(ServerHttpRequest request) {
        String path = request.getURI().getPath();
        Set<String> unAuthUrls = this.gatewaySecurityConfig.getUnAuthList();
        Iterator<String> var4 = unAuthUrls.iterator();

        String unAuthUrl;
        String currMethod;
        do {
            do {
                do {
                    if (!var4.hasNext()) {
                        return false;
                    }
                    unAuthUrl = var4.next();
                } while (StrUtil.isEmpty(unAuthUrl));

                currMethod = null;
                if (unAuthUrl.contains(BLANK_STR)) {
                    String[] array = unAuthUrl.split(BLANK_STR);
                    currMethod = array[0];
                    unAuthUrl = array[1];
                }
            } while (currMethod != null && !currMethod.equalsIgnoreCase(Objects.requireNonNull(request.getMethod()).name()));
        } while (!this.pathMatcher.match(unAuthUrl, path));

        return true;
    }

    private String utf8UrlEncode(String str) {
        return URLEncoder.encode(str, StandardCharsets.UTF_8);
    }


    private Mono<Void> successResult(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest mutableReq = exchange.getRequest().mutate()
                .header("userDTO", "")
                .header("token", "")
                .build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
        return chain.filter(mutableExchange);
    }

    private Mono<Void> errorResult(ServerWebExchange exchange, ITokenValidate tokenValidate, String userDefinedReturn) {
        Result<Object> fail = Result.fail(tokenValidate.getHttpStatus().toString());
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.setRawStatusCode(tokenValidate.getHttpStatus());

        //错误请求头构建
        JSONObject tokenHeaderJson = new JSONObject();
        tokenHeaderJson.put("cause", userDefinedReturn);
        tokenHeaderJson.put("detail", StrUtil.isBlank(userDefinedReturn) ? tokenValidate.getCause() : userDefinedReturn);
        serverHttpResponse.getHeaders().add("errMsg", utf8UrlEncode(tokenHeaderJson.toJSONString()));
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        //重定向登录页面
        String headerRedirect = request.getQueryParams().getFirst(REDIRECT_LOGIN_FLAG);
        boolean needRedirect = HttpMethod.GET.matches(request.getMethod().name()) && Boolean.TRUE.toString().equals(headerRedirect);
        if (needRedirect) {
            String realRequestUrl = exchange.getRequest().getURI().toString();
            StringBuilder redirectUrl = new StringBuilder(gatewaySecurityConfig.getDomain())
                    .append("/#/login/wechat?return_to=")
                    .append(realRequestUrl)
                    .append("?")
                    .append(request.getURI().getQuery());
            log.info("401且有redirect标识，跳转到redirectUrl:{}", redirectUrl);
            serverHttpResponse.getHeaders().set(HttpHeaders.LOCATION, redirectUrl.toString());
        }
        //数据返回响应
        byte[] data = JSON.toJSONString(fail).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(data);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -100;
    }
}
